﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UI;

public class MeasurementManager : MonoBehaviour
{
    public static MeasurementManager Instance;

    public bool measurementState = false;

    #region 测量线+点
    [Header("测量线")]
    public LineRenderer lineRenderer;

    [Header("吸附状态下的点图片")]
    public Sprite pointSprite;

    [Header("默认状态下的点图片")]
    public Sprite verticeSprite;

    [Header("长度文本")]
    public GameObject disTextPrefab;

    public List<MeasurementLine> lines = new List<MeasurementLine>();

    private List<GameObject> lineObjs = new List<GameObject>();

    private MeasurementLine nowLine;

    private int pointIndex = -1;

    /// <summary>
    /// 当前选中构件上的所有顶点
    /// </summary>
    private List<Vector3> hitobjVertexs = new List<Vector3>();

    private Vector3 hitPos;

    /// <summary>
    /// 鼠标选中的对象
    /// </summary>
    private GameObject hitObj;
    public GameObject HitObj
    {
        get
        {
            return hitObj;
        }
        set
        {
            if (hitObj != value)
            {
                hitObj = value;

                Debug.Log("更换选中对象");

                if (offset > 0)
                {
                    StopAllCoroutines();

                    StartCoroutine(GetHitObjVertices());
                }
            }
        }
    }
    #endregion

    #region 自动缩放
    [Header("标识点的自动缩放比例")]
    public float pointScaleCoe = 100.0f;

    [Header("长度线的自动缩放比例")]
    public float lineScaleCoe = 300.0f;

    [Header("标识点的缩放比例最大值")]
    public float maxScale;

    [Header("标识点的缩放比例最小值")]
    public float minScale;

    [Header("吸附检测范围")]
    public float offset;

    [Header("最大吸附检测范围")]
    public float maxOffset;

    [Header("最小吸附检测范围")]
    public float minOffset;

    [Header("吸附检测范围变化系数")]
    public float offsetChangeCoe;

    private float nowDis, oldDis;
    #endregion

    #region 垂足
    [Header("垂足(水平+竖直)辅助线")]
    public LineRenderer helpLine;

    [Header("垂足点")]
    public SpriteRenderer helpMark;

    public Transform pedalImage1;

    /// <summary>
    /// 垂足点位置
    /// </summary>
    private Vector3 pedalPos;

    private bool isPedal = false;
    #endregion

    #region x y z
    public LineRenderer lineX;

    public LineRenderer lineY;

    public LineRenderer lineZ;

    public RectTransform xText;

    public RectTransform yText;

    public RectTransform zText;

    public Transform pedalImage2;

    public Transform pedalImage3;

    private Vector3 pedalPos1, pedalPos2;

    private string nowHelpLineUuid = "";

    public bool isOpenXYZ = false;
    #endregion

    private Transform cameraTrans;

    private void Awake()
    {
        Instance = this;
    }

    private void Start()
    {
        cameraTrans = Camera.main.transform;

        helpLine.gameObject.SetActive(false);

        helpMark.gameObject.SetActive(false);

        CloseLine_XYZ();
    }

    private void Update()
    {
        nowDis = Vector3.Distance(cameraTrans.position, hitPos);

        if (oldDis == 0 || oldDis != nowDis)
        {
            offset = nowDis / offsetChangeCoe;

            offset = Mathf.Clamp(offset, minOffset, maxOffset);

            oldDis = nowDis;
        }
    }

    private void OnDestroy()
    {
        //Global.Measurement = false;

        ExitMeasurement();
    }

    /// <summary>
    /// 开始测量
    /// </summary>
    public void StartMeasurement()
    {
        measurementState = true;
    }

    /// <summary>
    /// 退出测量模式
    /// </summary>
    public void ExitMeasurement()
    {
        measurementState = false;

        if (pointIndex != -1 || nowLine != null)
        {
            Destroy(nowLine.startMark);

            Destroy(nowLine.endMark);

            Destroy(nowLine.disText);

            Destroy(nowLine.line.gameObject);

            var result = lineObjs.Find(s => s.name == nowLine.uuid);
            if (result != null)
            {
                int index = lineObjs.IndexOf(result);

                Destroy(result);

                lineObjs.RemoveAt(index);
            }

            pointIndex = -1;

            nowLine = null;
        }

        CloseLine_XYZ();

        CloseLine_Help();
    }

    /// <summary>
    /// 清空当前测量线
    /// </summary>
    public void ClearAll()
    {
        for (int i = 0; i < lineObjs.Count; i++)
        {
            Destroy(lineObjs[i]);
        }

        lineObjs.Clear();

        lines.Clear();
    }

    #region 点线操作
    /// <summary>
    /// 确认一个点位
    /// </summary>
    /// <param name="location"></param>
    /// <param name="dir"></param>
    public void EnsureOnePoint(Vector3 location, Vector3 dir)
    {
        if (nowLine == null)
        {
            CreateLine();
        }

        switch (pointIndex)
        {
            case -1:
                nowLine.start = location;

                nowLine.startDir = dir;

                CreateMark(location, dir, 1);

                ShowLine_Help(location, dir);

                pointIndex = 1;

                ShowLine();

                CloseLine_XYZ();

                break;
            case 1:
                nowLine.end = location;

                nowLine.endDir = dir;

                CreateMark(location, dir, 2);

                CloseLine_Help();

                pointIndex = 2;

                ShowLine();

                ShowLine_XYZ();

                FindObjectOfType<MeasurementPanel>().UpdateScroll();

                nowLine = null;

                pointIndex = -1;

                break;
        }
    }

    /// <summary>
    /// 线段结束点的实时跟随
    /// </summary>
    /// <param name="location"></param>
    public void ChangingOnePoint(Vector3 location)
    {
        switch (pointIndex)
        {
            case -1:
                return;
            case 1:
                nowLine.end = location;

                ShowLine(true);
                break;
        }
    }

    /// <summary>
    /// 显示测量线
    /// </summary>
    private void ShowLine(bool update = false)
    {
        if (nowLine == null)
        {
            return;
        }

        switch (pointIndex)
        {
            case 1:
                nowLine.line.SetPosition(0, nowLine.start);

                if (update)
                {
                    nowLine.line.gameObject.SetActive(true);

                    nowLine.disText.gameObject.SetActive(true);
                }

                break;
            case 2:

                break;
        }

        nowLine.line.SetPosition(1, nowLine.end);

        nowLine.line.startWidth = Vector3.Distance(nowLine.start, Camera.main.transform.position) * lineScaleCoe;

        nowLine.line.endWidth = Vector3.Distance(nowLine.end, Camera.main.transform.position) * lineScaleCoe;

        nowLine.dis = Vector3.Distance(nowLine.start, nowLine.end);

        nowLine.disMesh.text = (nowLine.dis * 1000).ToString("0") + "mm";

        nowLine.disRect.sizeDelta = new Vector2(nowLine.disMesh.text.Length * 10, nowLine.disRect.sizeDelta.y);

        nowLine.disText.transform.position = (nowLine.start + nowLine.end) / 2;
    }

    /// <summary>
    /// 生成测量线
    /// </summary>
    private void CreateLine()
    {
        nowLine = new MeasurementLine();

        nowLine.uuid = System.Guid.NewGuid().ToString();

        GameObject nowLineParent = new GameObject(nowLine.uuid);

        nowLineParent.transform.SetParent(transform);

        nowLine.line = Instantiate(lineRenderer, nowLineParent.transform);

        nowLine.line.positionCount = 2;

        nowLine.line.gameObject.SetActive(false);

        nowLine.startMark = Instantiate(Resources.Load<GameObject>("Circle"), nowLineParent.transform);

        nowLine.startMark.SetActive(false);

        nowLine.endMark = Instantiate(Resources.Load<GameObject>("Circle"), nowLineParent.transform);

        nowLine.endMark.SetActive(false);

        nowLine.disText = Instantiate(disTextPrefab, nowLineParent.transform);

        nowLine.disRect = nowLine.disText.GetComponent<RectTransform>();

        nowLine.disText.SetActive(false);

        nowLine.disMesh = nowLine.disText.GetComponentInChildren<Text>();

        lines.Add(nowLine);

        lineObjs.Add(nowLineParent);
    }

    /// <summary>
    /// 删除测量线
    /// </summary>
    /// <param name="uuid"></param>
    public void DeleteLine(string uuid)
    {
        var result = lines.Find(s => s.uuid == uuid);

        if (result != null)
        {
            lines.Remove(result);
        }

        var resultObj = lineObjs.Find(s => s.name == uuid);

        if (resultObj != null)
        {
            int index = lineObjs.IndexOf(resultObj);

            Destroy(resultObj);

            lineObjs.RemoveAt(index);
        }

        if (uuid == nowHelpLineUuid)
        {
            CloseLine_XYZ();

            nowHelpLineUuid = "";
        }
    }

    /// <summary>
    /// 生成测量点
    /// </summary>
    /// <param name="pos">位置</param>
    /// <param name="dir">点所在面的法线方向</param>
    private void CreateMark(Vector3 pos, Vector3 dir, int index)
    {
        if (index == 1)
        {
            nowLine.startDir = dir;

            nowLine.startMark.GetComponent<SpriteRenderer>().color = Color.blue;

            nowLine.startMark.SetActive(true);

            nowLine.startMark.transform.position = pos;

            nowLine.startMark.transform.localScale = Vector3.one * (Vector3.Distance(pos, cameraTrans.position) * pointScaleCoe) * 0.5f;

            nowLine.startMark.transform.forward = nowLine.startDir;
        }
        else
        {
            nowLine.endDir = dir;

            nowLine.endMark.GetComponent<SpriteRenderer>().color = Color.blue;

            nowLine.endMark.SetActive(true);

            nowLine.endMark.transform.position = pos;

            nowLine.endMark.transform.localScale = Vector3.one * (Vector3.Distance(pos, cameraTrans.position) * pointScaleCoe) * 0.5f;

            nowLine.endMark.transform.forward = nowLine.endDir;
        }
    }

    /// <summary>
    /// 调节点线缩放比例
    /// </summary>
    /// <param name="value"></param>
    public void SetMarkObjScale(float value)
    {
        if (lines.Count <= 0) return;

        pointScaleCoe = value;

        lineScaleCoe = value / 10f;

        foreach (var item in lines)
        {
            item.startMark.transform.localScale = Vector3.one * (Vector3.Distance(item.start, cameraTrans.position) * pointScaleCoe);

            item.endMark.transform.localScale = Vector3.one * (Vector3.Distance(item.end, cameraTrans.position) * pointScaleCoe);

            item.line.startWidth = Vector3.Distance(item.start, cameraTrans.position) * lineScaleCoe;

            item.line.endWidth = Vector3.Distance(item.end, cameraTrans.position) * lineScaleCoe;
        }
    }
    #endregion

    #region 顶点吸附
    private IEnumerator GetHitObjVertices()
    {
        yield return new WaitForEndOfFrame();

        hitobjVertexs.Clear();

        if (HitObj != null)
        {
            MeshFilter meshFilter = HitObj.GetComponent<MeshFilter>();

            if (meshFilter != null)
            {
                Mesh mesh = meshFilter.mesh;

                Debug.Log("开始填充顶点数据,顶点数量：" + mesh.vertices.Length);

                for (int i = 0; i < mesh.vertices.Length; i++)
                {
                    Vector3 verticesPos = HitObj.transform.TransformPoint(mesh.vertices[i]);

                    if (!hitobjVertexs.Contains(verticesPos))
                    {
                        hitobjVertexs.Add(verticesPos);
                    }
                }

                Debug.Log("顶点数据填充完成");
            }
        }

        GC.Collect();
    }

    /// <summary>
    /// 判断鼠标是否接近顶点
    /// </summary>
    public (bool, Vector3) IsVertices(Vector3 pos)
    {
        hitPos = pos;

        if (offset <= 0) return (false, Vector3.zero);

        List<Vector3> vertices = hitobjVertexs;

        if (isPedal) vertices.Add(pedalPos);

        List<Vector3> nearVertices = new List<Vector3>();

        for (int i = 0; i < vertices.Count; i++)
        {
            float distance = Mathf.Abs(Vector3.Distance(vertices[i], hitPos));

            if (distance < offset)
            {
                nearVertices.Add(vertices[i]);
            }
        }

        if (nearVertices.Count > 1)
        {
            return (true, SelecteNearest(nearVertices));
        }
        else if (nearVertices.Count == 1)
        {
            return (true, nearVertices[0]);
        }
        else
        {
            return (false, Vector3.zero);
        }
    }

    /// <summary>
    /// 挑选最近的一个顶点
    /// </summary>
    public Vector3 SelecteNearest(List<Vector3> nearVertices)
    {
        Vector3 nearest = Vector3.zero;

        Dictionary<float, Vector3> keyValuePairs = new Dictionary<float, Vector3>();

        List<float> eligibles = new List<float>();

        List<float> tests = new List<float>();

        for (int i = 0; i < nearVertices.Count; i++)
        {
            float distance = Mathf.Abs(Vector3.Distance(nearVertices[i], hitPos));

            tests.Add(distance);

            tests.Sort();

            Debug.LogWarning(tests[0] + "----" + offset);

            if (distance < offset)
            {
                if (!eligibles.Contains(distance))
                {
                    eligibles.Add(distance);
                    if (!keyValuePairs.ContainsKey(distance))
                    {
                        keyValuePairs.Add(distance, nearVertices[i]);
                    }
                }
            }
        }

        if (eligibles.Count < 1) return hitPos;

        if (eligibles.Count == 1)
        {
            nearest = keyValuePairs[eligibles[0]];
        }
        else if (eligibles.Count > 1)
        {
            nearest = keyValuePairs[Sort(eligibles)[0]];
        }

        return nearest;
    }

    /// <summary>
    /// 排序
    /// </summary>
    /// <param name="arr"></param>
    /// <returns></returns>
    public List<float> Sort(List<float> arr)
    {
        float j, temp;
        bool done = false;
        j = 1;
        while ((j < arr.Count) && (!done))//判断长度    
        {
            done = true;
            for (int i = 0; i < arr.Count - j; i++)
            {
                if (arr[i] > arr[i + 1])
                {
                    done = false;
                    temp = arr[i];
                    arr[i] = arr[i + 1];//交换数据    
                    arr[i + 1] = temp;
                }
            }
            j++;
        }

        return arr;
    }
    #endregion

    #region 垂足

    /// <summary>
    /// 显示辅助线
    /// </summary>
    /// <param name="pos"></param>
    /// <param name="dir"></param>
    public void ShowLine_Help(Vector3 pos, Vector3 dir)
    {
        if (Physics.Raycast(pos, dir, out RaycastHit hit, 1000))
        {
            Vector3 hitDir = hit.normal;

            helpLine.positionCount = 2;

            helpLine.SetPosition(0, pos);

            helpLine.SetPosition(1, hit.point);

            helpLine.startWidth = Vector3.Distance(pos, cameraTrans.position) * lineScaleCoe * 0.5f;

            helpLine.endWidth = Vector3.Distance(hit.point, cameraTrans.position) * lineScaleCoe * 0.5f;

            helpMark.transform.forward = hit.normal;

            helpMark.transform.position = hit.point;

            helpMark.transform.localScale = Vector3.one * (Vector3.Distance(hit.point, cameraTrans.position) * pointScaleCoe);

            isPedal = true;

            pedalPos = hit.point;

            helpLine.gameObject.SetActive(true);

            helpMark.gameObject.SetActive(true);
        }
    }

    /// <summary>
    /// 关闭辅助线
    /// </summary>
    public void CloseLine_Help()
    {
        helpLine.gameObject.SetActive(false);

        helpMark.gameObject.SetActive(false);

        isPedal = false;

        pedalPos = Vector3.zero;
    }
    #endregion

    #region x y z 轴距离
    public void ShowLine_XYZ()
    {
        if (nowLine == null || !isOpenXYZ)
        {
            return;
        }

        nowHelpLineUuid = nowLine.uuid;

        Vector3 middle = nowLine.end - nowLine.start;

        Vector3 middleabs = new Vector3(MathF.Abs(middle.x), MathF.Abs(middle.y), MathF.Abs(middle.z));

        if (middleabs.x > 0.001f && middleabs.y < 0.001f && middleabs.z < 0.001f)
        {
            return;
        }

        if (middleabs.x < 0.001f && middleabs.y > 0.001f && middleabs.z < 0.001f)
        {
            return;
        }

        if (middleabs.x < 0.001f && middleabs.y < 0.001f && middleabs.z > 0.001f)
        {
            return;
        }

        lineX.gameObject.SetActive(middleabs.x > 0.001f);

        lineY.gameObject.SetActive(middleabs.y > 0.001f);

        lineZ.gameObject.SetActive(middleabs.z > 0.001f);

        xText.gameObject.SetActive(middleabs.x > 0.001f);

        yText.gameObject.SetActive(middleabs.y > 0.001f);

        zText.gameObject.SetActive(middleabs.z > 0.001f);

        Vector3 maxYPos = nowLine.start.y < nowLine.end.y ? nowLine.end : nowLine.start;

        Vector3 minYPos = nowLine.start.y > nowLine.end.y ? nowLine.end : nowLine.start;

        Vector3 maxZPos = nowLine.start.z < nowLine.end.z ? nowLine.end : nowLine.start;

        Vector3 minZPos = nowLine.start.z > nowLine.end.z ? nowLine.end : nowLine.start;

        if (middleabs.x > 0.001f && middleabs.y > 0.001f && middleabs.z > 0.001f)
        {
            pedalPos1 = new Vector3(minYPos.x, minYPos.y, maxYPos.z);

            pedalPos2 = new Vector3(maxYPos.x, minYPos.y, maxYPos.z);

            pedalImage2.gameObject.SetActive(true);

            pedalImage3.gameObject.SetActive(true);

            pedalImage2.transform.position = pedalPos1;

            pedalImage3.transform.position = pedalPos2;

            pedalImage2.forward = Vector3.Cross(minYPos - pedalPos1, pedalPos1 - pedalPos2).normalized;

            pedalImage3.forward = Vector3.Cross(pedalPos1 - pedalPos2, maxYPos - pedalPos2).normalized;

            ShowLineX(pedalPos2, pedalPos1);

            ShowLineY(pedalPos2, maxYPos);

            ShowLineZ(minYPos, pedalPos1);
        }
        else 
        {
            pedalImage2.gameObject.SetActive(true);

            if (middleabs.x > 0.001f && middleabs.y > 0.001f && middleabs.z < 0.001f)
            {
                pedalPos1 = new Vector3(maxYPos.x, minYPos.y, minYPos.z);

                ShowLineX(minYPos, pedalPos1);

                ShowLineY(pedalPos1, maxYPos);

                pedalImage2.forward = Vector3.Cross(minYPos - pedalPos1, maxYPos - pedalPos1).normalized;
            }
            else if (middleabs.x > 0.001f && middleabs.y < 0.001f && middleabs.z > 0.001f)
            {
                pedalPos1 = new Vector3(maxZPos.x, minYPos.y, minZPos.z);

                ShowLineX(minZPos, pedalPos1);

                ShowLineZ(pedalPos1, maxZPos);

                pedalImage2.forward = Vector3.Cross(minZPos - pedalPos1, maxZPos - pedalPos1).normalized;

                if (minZPos.x < pedalPos1.x)
                {
                    pedalImage2.transform.Rotate(0, 0, 180);
                }
            }
            else if (middleabs.x < 0.001f && middleabs.y > 0.001f && middleabs.z > 0.001f)
            {
                pedalPos1 = new Vector3(maxYPos.x, minYPos.y, maxYPos.z);

                ShowLineY(nowLine.start, pedalPos1);

                ShowLineZ(pedalPos1, nowLine.end);

                pedalImage2.forward = Vector3.Cross(minYPos - pedalPos1, maxYPos - pedalPos1).normalized;
            }

            pedalImage2.transform.position = pedalPos1;
        }
    }

    private void ShowLineX(Vector3 pos1, Vector3 pos2)
    {
        lineX.gameObject.SetActive(true);

        lineX.SetPosition(0, pos1);

        lineX.SetPosition(1, pos2);

        lineX.startWidth = Vector3.Distance(pos1, cameraTrans.position) * lineScaleCoe;

        lineX.startWidth = Vector3.Distance(pos2, cameraTrans.position) * lineScaleCoe;

        xText.position = (pos1 + pos2) * 0.5f;

        xText.GetComponentInChildren<Text>().text = "x: " + (Mathf.Abs(nowLine.end.x - nowLine.start.x) * 1000).ToString("0") + "mm";

        xText.sizeDelta = new Vector2(xText.GetComponentInChildren<Text>().text.Length * 10, xText.sizeDelta.y);
    }

    private void ShowLineY(Vector3 pos1, Vector3 pos2)
    {
        lineY.gameObject.SetActive(true);

        lineY.SetPosition(0, pos1);

        lineY.SetPosition(1, pos2);

        lineY.startWidth = Vector3.Distance(pos1, cameraTrans.position) * lineScaleCoe;

        lineY.startWidth = Vector3.Distance(pos2, cameraTrans.position) * lineScaleCoe;

        yText.position = (pos1 + pos2) * 0.5f;

        yText.GetComponentInChildren<Text>().text = "z: " + (Mathf.Abs(nowLine.end.y - nowLine.start.y) * 1000).ToString("0") + "mm";

        yText.sizeDelta = new Vector2(yText.GetComponentInChildren<Text>().text.Length * 10, xText.sizeDelta.y);
    }

    private void ShowLineZ(Vector3 pos1, Vector3 pos2)
    {
        lineZ.gameObject.SetActive(true);

        lineZ.SetPosition(0, pos1);

        lineZ.SetPosition(1, pos2);

        lineZ.startWidth = Vector3.Distance(pos1, cameraTrans.position) * lineScaleCoe;

        lineZ.startWidth = Vector3.Distance(pos2, cameraTrans.position) * lineScaleCoe;

        zText.position = (pos1 + pos2) * 0.5f;

        zText.GetComponentInChildren<Text>().text = "y: " + (Mathf.Abs(nowLine.end.z - nowLine.start.z) * 1000).ToString("0") + "mm";

        zText.sizeDelta = new Vector2(zText.GetComponentInChildren<Text>().text.Length * 10, xText.sizeDelta.y);
    }

    private Vector3 AbsVector3(Vector3 v)
    {
        return new Vector3(Mathf.Abs(v.x), Mathf.Abs(v.y), Mathf.Abs(v.z));
    }

    public void CloseLine_XYZ()
    {
        lineX.gameObject.SetActive(false);

        lineY.gameObject.SetActive(false);

        lineZ.gameObject.SetActive(false);

        xText.gameObject.SetActive(false);

        yText.gameObject.SetActive(false);

        zText.gameObject.SetActive(false);

        pedalImage2.gameObject.SetActive(false);

        pedalImage3.gameObject.SetActive(false);
    }
    #endregion
}

/// <summary>
/// 测量线
/// </summary>
public class MeasurementLine
{
    public string uuid;

    public Vector3 start;

    public Vector3 startDir;

    public Vector3 end;

    public Vector3 endDir;

    public float dis;

    public LineRenderer line;

    public GameObject startMark;

    public GameObject endMark;

    public GameObject disText;

    public RectTransform disRect;

    public Text disMesh;
}
